import random

from django import forms
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django_redis import get_redis_connection

from utils import encrypt
from utils.tencent.sms import send_sms_single
from web import models
from web.forms.bootstrap import BootStrapForm


class RegisterModelForm(BootStrapForm, forms.ModelForm):
    password = forms.CharField(
        label='密码',
        widget=forms.PasswordInput(),
        min_length=8, max_length=64,
        error_messages={
            'min_length': '密码长度不能小于8个字符',
            'max_length': '密码长度不能超过64个字符',
        },
    )
    confirm_password = forms.CharField(
        label='重复密码',
        # 可以使用这种attrs的方式，也可以用__init__的方式
        widget=forms.PasswordInput(attrs={'placeholder': '请重复一遍密码'}),
    )
    code = forms.IntegerField(label='验证码', max_value=9999, min_value=1000)

    class Meta:
        model = models.UserInfo

        # fields写成‘__all__’会默认顺序展示，如果想更换顺序，需要自己写
        # fields = '__all__'
        fields = ['username', 'email', 'password', 'confirm_password', 'mobile_phone', 'code']

    def clean_username(self):
        """钩子函数，用于校验用户名是否重复"""
        username = self.cleaned_data['username']
        is_exists = models.UserInfo.objects.filter(username=username).exists()
        if is_exists:
            raise ValidationError('用户名已被使用')
        return username

    def clean_password(self):
        pwd = self.cleaned_data['password']
        # 加密并返回密码。现在去utils中造一个加密函数
        return encrypt.md5(pwd)

    def clean_confirm_password(self):
        """用于校验2次密码是否一致的钩子函数"""
        pwd = self.cleaned_data['password']  # 这个地方cleaned_data拿到的是已经加密的pwd
        confirm_pwd = encrypt.md5(self.cleaned_data['confirm_password'])
        if pwd != confirm_pwd:
            raise ValidationError('两次密码不一致')
        return confirm_pwd

    def clean_mobile_phone(self):
        mobile_phone = self.cleaned_data['mobile_phone']
        is_exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()
        if is_exists:
            raise ValidationError('该手机号已注册！')
        return mobile_phone

    def clean_code(self):
        code = self.cleaned_data['code']
        mobile = self.cleaned_data.get('mobile_phone')
        if not mobile:
            return code
        conn = get_redis_connection()
        redis_code = conn.get(mobile)
        if not redis_code:
            raise ValidationError('验证码失效，请重新获取！')
        if redis_code.decode('utf-8') == str(code).strip():
            return code
        else:
            raise ValidationError('验证码错误，请重新输入！')


class SendSmsForm(forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^1[3,4,5,6,7,8]\d{9}$', '手机号格式错误'), ])

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def clean_mobile_phone(self):
        """进行手机号校验的钩子"""
        mobile_phone = self.cleaned_data['mobile_phone']

        # 校验request.GET.get('tpl')，看这个短信模板是否正确
        tpl = self.request.GET.get('tpl')
        template_id = settings.TENCENT_SMS_TEMPLATE.get(tpl)
        if not template_id:
            raise ValidationError('短信模板错误')

        # 检验数据库中是不是已经存在了该手机号
        is_exists = models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists()

        if tpl == 'login':
            if not is_exists:
                raise ValidationError('该手机号尚未注册')
        elif tpl == 'register':
            if is_exists:
                raise ValidationError('该手机号已注册')

        # 发短信+验证码写入redis
        code = random.randrange(1000, 10000)
        sms = send_sms_single(mobile_phone, template_id, [code, ])
        # 发送短信
        if sms['result'] != 0:
            raise ValidationError('短信发送失败，{}'.format(sms['errmsg']))
        # 验证码写入redis
        conn = get_redis_connection()
        conn.set(mobile_phone, code, ex=60)

        return mobile_phone


class LoginSMSForm(BootStrapForm, forms.Form):
    mobile_phone = forms.CharField(label='手机号', validators=[RegexValidator(r'^1[3,4,5,6,7,8]\d{9}$', '手机号格式错误'), ])
    # code = forms.IntegerField(label='验证码', max_value=9999, min_value=1000)
    code = forms.IntegerField(label='验证码', max_value=9999)

    def clean_mobile_phone(self):
        mobile_phone = self.cleaned_data['mobile_phone']
        # if models.UserInfo.objects.filter(mobile_phone=mobile_phone).exists():
        user_object = models.UserInfo.objects.filter(mobile_phone=mobile_phone).first()
        if user_object:
            return user_object
        else:
            raise ValidationError('手机号不存在')

    def clean_code(self):
        code = self.cleaned_data['code']
        # mobile_phone = self.cleaned_data.get('mobile_phone')  # 这个地方get到的数据应该是个用户对象，不是手机号
        mobile_phone = self.cleaned_data.get('mobile_phone').mobile_phone
        if not mobile_phone:  # 如果手机号不存在，就没必要再去redis中进行校验
            return code
        conn = get_redis_connection()
        redis_code = conn.get(mobile_phone)
        if not redis_code:
            raise ValidationError('验证码失效，请重新获取！')
        if redis_code.decode('utf-8') == str(code).strip():
            return code
        else:
            raise ValidationError('验证码错误，请重新输入！')


class LoginForm(BootStrapForm, forms.Form):
    username = forms.CharField(label='用户名或手机号')
    password = forms.CharField(
        label='密码',
        widget=forms.PasswordInput(render_value=True),
        min_length=8, max_length=64,
        error_messages={
            'min_length': '密码错误',
            'max_length': '密码错误',
        },
    )
    code = forms.CharField(label='图片验证码')

    def __init__(self, request, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request = request

    def clean_password(self):
        pwd = self.cleaned_data['password']
        return encrypt.md5(pwd)

    def clean_code(self):
        """
        钩子函数，用于校验登录时输入的验证码是否正确
        :return:
        """
        ipt_code = self.cleaned_data['code']
        img_code = self.request.session.get('img_code')
        if not img_code:
            raise ValidationError('验证码已失效，请重新获取')
        if ipt_code.lower() != img_code.lower():
            raise ValidationError('验证码错误，请重新输入')
        return img_code